#include "trap.h"
#include "mmu.h"
#include "proc.h"
#include "riscv.h"
#include "stddef.h"
#include "syscall.h"
#include "type.h"
#include "plic.h"
#include "stdio.h"
#include "time.h"
#include "console.h"
#include "mmulayout.h"
#include "sbi.h"
#include "fs/disk.h"

#include "dbg.h"

extern char trampoline[], uservec[], userret[];  // don't change char [], if use byte_t *, cause some problems(may because assmebler)
extern void kernelvec();

// void kervec(void)
// {
//     printf("kernel trap happen\n");
//     kernelvec();
// }
void
trapinithart(void) {
    __DEBUG_FUNC_START;
    w_stvec((register_t)kernelvec);
    w_sstatus(r_sstatus() | SSTATUS_SIE);
    w_sie(r_sie() | SIE_SEIE | SIE_SSIE | SIE_STIE);
#ifndef DEBUG
    setNextTime();
#endif
    //  sbi_set_extern_interrupt((address_t)hardwareInterrupt);
    __DEBUG_FUNC_END;
}
void
usertrap(void) {
    __DEBUG_INFO(usertrap());
    register_t scause = r_scause();
    w_stvec((register_t)kernelvec);
    proc_t* p = getCurrentProc();
    p->tms.entry_kernel = r_time();
    if (p->tms.entry_kernel > p->tms.leave_kernel) {
#ifdef QEMU
        p->tms.tms_utime += (p->tms.entry_kernel - p->tms.leave_kernel) / (100000);
#else
        p->tms.tms_utime += (p->tms.entry_kernel - p->tms.leave_kernel) / (100000 * 0.65);
#endif
        if (p->parent != NULL) {
            p->parent->tms.tms_cutime = p->tms.tms_utime;
        }
    }
    p->trapFrame->u_pc = r_sepc();
    if (scause == 8) {
        p->trapFrame->u_pc += 4;
        intr_on();
        syscall();
    } else {
        // if(hardwareInterrupt() == -1)//TODO
        {
            printf("unknown interrupt\n");
            printf("scause: %x\n", scause);
            printf("sepc: %x\n", r_sepc());
            while (1)
                ;
        }
    }
    usertrapret();
}

void
usertrapret(void) {
    proc_t* p = getCurrentProc();
    intr_off();
    w_stvec(TRAMPOLINE + ((register_t)uservec - (register_t)trampoline));
    p->trapFrame->kernel_satp = r_satp();
    p->trapFrame->kernel_sp = (address_t)p->kstack + PAGE_SIZE;
    p->trapFrame->user_trap_address = (address_t)usertrap;

    uint64_t sstatus = r_sstatus();
    sstatus = sstatus & (~SSTATUS_SSP);
    w_sstatus(sstatus);
    intr_on();
    w_sepc(p->trapFrame->u_pc);
    address_t satp = PAGE_TABLE_SHIFT(p->upageTable);
    address_t fn = TRAMPOLINE + ((register_t)userret - (register_t)trampoline);
    // address_t fn = TRAMPOLINE;
    // address_t fn = TRAMRET;
    p->tms.leave_kernel = r_time();
    if (p->tms.leave_kernel > p->tms.entry_kernel) {
#ifdef QEMU
        p->tms.tms_stime += (p->tms.leave_kernel - p->tms.entry_kernel) / (100000);
#else
        p->tms.tms_stime += (p->tms.leave_kernel - p->tms.entry_kernel) / (100000 * 0.65);
#endif
        if (p->parent != NULL) {
            p->tms.tms_cstime = p->tms.tms_stime;
        }
    }
    void* address = (void*)kvm_trans(fn, p->kpageTable);  // TODO
    ((void (*)(register_t, register_t))fn)(TRAP_FRAME, satp);
}

uint32_t
hardwareInterrupt(void) {
    // printf("extern trap");

    register_t scause = r_scause();
#ifdef QEMU
    if (scause == 0x8000000000000009)
#else
    if ((scause == 0X8000000000000001L) && (r_stval() == 9))
#endif
    {
        int ret;
        uint64_t irq = plic_claim();
        if (irq == UART_IRQ) {
            int c = sbi_getchar();
            if (c != -1)
                console_intr(c);
            ret = 1;
        } else if (irq == DISK_IRQ) {
            disk_intr();
            ret = 2;
        } else if (irq) {
            printf("unexpected interrupt irq = %d\n", irq);
            ret = 0;
        }
        if (irq)
            plic_complete(irq);
#ifndef QEMU
        w_sip(r_sip() & ~2);
        sbi_set_mie();
#endif
        return ret;
    } else if (scause == 0x8000000000000005) {
        // #ifdef DEBUG
        //         printf("receive time trap\n");
        // #endif
        setNextTime();
        proc_t* proc = getCurrentProc();
        if ((proc != NULL) && (proc->state == RUNNING)) {
            yield();
        }
        return 3;
    } else {
        printf("unexpected interrupt scause:0x%x\n",scause);
        return 0;
    }
}

void
kernel_trap() {
    //   printf("kernel_trap");
    address_t sepc = r_sepc();
    address_t sstatus = r_sstatus();

    hardwareInterrupt();

    w_sepc(sepc);
    w_sstatus(sstatus);
}
void
intr_off(void) {
    w_sstatus(r_sstatus() & (~SSTATUS_SIE));
}

void
intr_on(void) {
    w_sstatus(r_sstatus() | SSTATUS_SIE);
}

uint32_t
intr_get(void) {
    return (r_sstatus() & SSTATUS_SIE) != 0;
}
void
intr_pop(void) {
    cpu_t* c = getCurrentCPU();
    if (intr_get()) {
        panic("intr_pop: interruptable");
        while (1)
            ;
    }
    if (c->lk_num < 1) {
        panic("intr_pop: no push before");
        while (1)
            ;
    }
    c->lk_num--;
    if ((c->lk_num == 0) && (c->before_lk_intr)) {
        intr_on();
    }
}

void
intr_push(void) {
    intr_off();
    cpu_t* c = getCurrentCPU();
    if (c->lk_num == 0) {
        c->before_lk_intr = intr_get();
    }
    c->lk_num++;
}